//===--- driver.cpp - Swift Compiler Driver -------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This is the entry point to the swift compiler driver.
//
//===----------------------------------------------------------------------===//

#include "polarphp/ast/DiagnosticEngine.h"
#include "polarphp/basic/LLVMInitialize.h"
#include "polarphp/basic/PrettyStackTrace.h"
#include "polarphp/basic/Program.h"
#include "polarphp/basic/TaskQueue.h"
#include "polarphp/basic/SourceMgr.h"
#include "polarphp/driver/Compilation.h"
#include "polarphp/driver/Driver.h"
#include "polarphp/driver/FrontendUtil.h"
#include "polarphp/driver/Job.h"
#include "polarphp/driver/ToolChain.h"
#include "polarphp/frontend/Frontend.h"
#include "polarphp/frontend/PrintingDiagnosticConsumer.h"
#include "polarphp/frontendtool/FrontendTool.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/Errno.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/ManagedStatic.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Program.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/StringSaver.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/raw_ostream.h"

#include <memory>
#include <stdlib.h>

#if defined(_WIN32)
#include <windows.h>
#endif

using namespace polar;
using namespace polar::driver;

std::string getExecutablePath(const char *FirstArg) {
   void *P = (void *)(intptr_t)getExecutablePath;
   return llvm::sys::fs::getMainExecutable(FirstArg, P);
}

/// Run 'polarphp-autolink-extract'.
//extern int autolink_extract_main(ArrayRef<const char *> Args, const char *Argv0,
//                                 void *MainAddr);

//extern int modulewrap_main(ArrayRef<const char *> Args, const char *Argv0,
//                           void *MainAddr);

/// Run 'polarphp-indent'
//extern int polarphp_indent_main(ArrayRef<const char *> Args, const char *Argv0,
//                             void *MainAddr);

/// Determine if the given invocation should run as a subcommand.
///
/// \param ExecName The name of the argv[0] we were invoked as.
/// \param SubcommandName On success, the full name of the subcommand to invoke.
/// \param Args On return, the adjusted program arguments to use.
/// \returns True if running as a subcommand.
static bool shouldRunAsSubcommand(StringRef ExecName,
                                  SmallString<256> &SubcommandName,
                                  const ArrayRef<const char *> Args,
                                  bool &isRepl) {
   assert(!Args.empty());

   // If we are not run as 'polarphp', don't do anything special. This doesn't work
   // with symlinks with alternate names, but we can't detect 'polarphp' vs 'polarphpc'
   // if we try and resolve using the actual executable path.
   if (ExecName != "polarphp")
      return false;

   // If there are no program arguments, always invoke as normal.
   if (Args.size() == 1)
      return false;

   // Otherwise, we have a program argument. If it looks like an option or a
   // path, then invoke in interactive mode with the arguments as given.
   StringRef FirstArg(Args[1]);
   if (FirstArg.startswith("-") || FirstArg.find('.') != StringRef::npos ||
       FirstArg.find('/') != StringRef::npos)
      return false;

   // Otherwise, we should have some sort of subcommand. Get the subcommand name
   // and remove it from the program arguments.
   StringRef Subcommand = Args[1];

   // If the subcommand is the "built-in" 'repl', then use the
   // normal driver.
   if (Subcommand == "repl") {
      isRepl = true;
      return false;
   }

   // Form the subcommand name.
   SubcommandName.assign("polarphp-");
   SubcommandName.append(Subcommand);

   return true;
}

static int run_driver(StringRef ExecName,
                      const ArrayRef<const char *> argv) {
   // Handle integrated tools.
   if (argv.size() > 1) {
      StringRef FirstArg(argv[1]);
      if (FirstArg == "-frontend") {
         return performFrontend(llvm::makeArrayRef(argv.data()+2,
                                                   argv.data()+argv.size()),
                                argv[0], (void *)(intptr_t)getExecutablePath);
      }
//      if (FirstArg == "-modulewrap") {
//         return modulewrap_main(llvm::makeArrayRef(argv.data()+2,
//                                                   argv.data()+argv.size()),
//                                argv[0], (void *)(intptr_t)getExecutablePath);
//      }
   }

   std::string Path = getExecutablePath(argv[0]);

   PrintingDiagnosticConsumer PDC;

   SourceManager SM;
   DiagnosticEngine Diags(SM);
   Diags.addConsumer(PDC);

   Driver TheDriver(Path, ExecName, argv, Diags);
   /// TODO
//   switch (TheDriver.getDriverKind()) {
//      case Driver::DriverKind::AutolinkExtract:
//         return autolink_extract_main(
//            TheDriver.getArgsWithoutProgramNameAndDriverMode(argv),
//            argv[0], (void *)(intptr_t)getExecutablePath);
//      case Driver::DriverKind::PolarphpIndent:
//         return polarphp_indent_main(
//            TheDriver.getArgsWithoutProgramNameAndDriverMode(argv),
//            argv[0], (void *)(intptr_t)getExecutablePath);
//      default:
//         break;
//   }

   std::unique_ptr<llvm::opt::InputArgList> ArgList =
      TheDriver.parseArgStrings(ArrayRef<const char*>(argv).slice(1));
   if (Diags.hadAnyError())
      return 1;

   std::unique_ptr<ToolChain> TC = TheDriver.buildToolChain(*ArgList);
   if (Diags.hadAnyError())
      return 1;

   std::unique_ptr<Compilation> C =
      TheDriver.buildCompilation(*TC, std::move(ArgList));

   if (Diags.hadAnyError())
      return 1;

   if (C) {
      std::unique_ptr<sys::TaskQueue> TQ = TheDriver.buildTaskQueue(*C);
      return C->performJobs(std::move(TQ));
   }

   return 0;
}

int main(int argc_, const char **argv_) {
#if defined(_WIN32)
   LPWSTR *wargv_ = CommandLineToArgvW(GetCommandLineW(), &argc_);
  std::vector<std::string> utf8Args;
  // We use UTF-8 as the internal character encoding. On Windows,
  // arguments passed to wmain are encoded in UTF-16
  for (int i = 0; i < argc_; i++) {
    const wchar_t *wideArg = wargv_[i];
    int wideArgLen = std::wcslen(wideArg);
    utf8Args.push_back("");
    llvm::ArrayRef<char> uRef((const char *)wideArg,
                              (const char *)(wideArg + wideArgLen));
    llvm::convertUTF16ToUTF8String(uRef, utf8Args[i]);
  }

  std::vector<const char *> utf8CStrs;
  std::transform(utf8Args.begin(), utf8Args.end(),
                 std::back_inserter(utf8CStrs),
                 std::mem_fn(&std::string::c_str));
  argv_ = utf8CStrs.data();
#endif
   // Expand any response files in the command line argument vector - arguments
   // may be passed through response files in the event of command line length
   // restrictions.
   SmallVector<const char *, 256> ExpandedArgs(&argv_[0], &argv_[argc_]);
   llvm::BumpPtrAllocator Allocator;
   llvm::StringSaver Saver(Allocator);
   llvm::cl::ExpandResponseFiles(
      Saver,
      llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows()
      ? llvm::cl::TokenizeWindowsCommandLine
      : llvm::cl::TokenizeGNUCommandLine,
      ExpandedArgs);

   // Initialize the stack trace using the parsed argument vector with expanded
   // response files.

   // PROGRAM_START/InitLLVM overwrites the passed in arguments with UTF-8
   // versions of them on Windows. This also has the effect of overwriting the
   // response file expansion. Since we handle the UTF-8 conversion above, we
   // pass in a copy and throw away the modifications.
   int ThrowawayExpandedArgc = ExpandedArgs.size();
   const char **ThrowawayExpandedArgv = ExpandedArgs.data();
   PROGRAM_START(ThrowawayExpandedArgc, ThrowawayExpandedArgv);
   ArrayRef<const char *> argv(ExpandedArgs);

   PrettyStackTracePolarphpVersion versionStackTrace;

   // Check if this invocation should execute a subcommand.
   StringRef ExecName = llvm::sys::path::stem(argv[0]);
   SmallString<256> SubcommandName;
   bool isRepl = false;
   if (shouldRunAsSubcommand(ExecName, SubcommandName, argv, isRepl)) {
      // Preserve argv for the stack trace.
      SmallVector<const char *, 256> subCommandArgs(argv.begin(), argv.end());
      subCommandArgs.erase(&subCommandArgs[1]);
      // We are running as a subcommand, try to find the subcommand adjacent to
      // the executable we are running as.
      SmallString<256> SubcommandPath(
         llvm::sys::path::parent_path(getExecutablePath(argv[0])));
      llvm::sys::path::append(SubcommandPath, SubcommandName);

      // If we didn't find the tool there, let the OS search for it.
      if (!llvm::sys::fs::exists(SubcommandPath)) {
         // Search for the program and use the path if found. If there was an
         // error, ignore it and just let the exec fail.
         auto result = llvm::sys::findProgramByName(SubcommandName);
         if (!result.getError())
            SubcommandPath = *result;
      }

      // Rewrite the program argument.
      subCommandArgs[0] = SubcommandPath.c_str();

      // Execute the subcommand.
      subCommandArgs.push_back(nullptr);
      execute_in_place(SubcommandPath.c_str(), subCommandArgs.data());

      // If we reach here then an error occurred (typically a missing path).
      std::string ErrorString = llvm::sys::StrError();
      llvm::errs() << "error: unable to invoke subcommand: " << subCommandArgs[0]
                   << " (" << ErrorString << ")\n";
      return 2;
   }

   if (isRepl) {
      // Preserve argv for the stack trace.
      SmallVector<const char *, 256> replArgs(argv.begin(), argv.end());
      replArgs.erase(&replArgs[1]);
      return run_driver(ExecName, replArgs);
   } else {
      return run_driver(ExecName, argv);
   }
}
